package com.iwhale.sequence.redisidx.sequence;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import com.iwhale.sequence.redisidx.config.RedisConfig;

import redis.clients.jedis.util.Hashing;

/**
 * 采用一致性hash算法客户端分片 记录各个客户端的状态 如果客户端状态异常，就使用下一个相邻的redis。 暂时不考虑redis宕机，然后再回复的问题。
 */
@Component
public class SequenceRedis {
    // logger
    private static Logger logger = LoggerFactory.getLogger(SequenceRedis.class);

    private TreeMap<Long, String> nodes = new TreeMap<>();

    // 记录每个序列最新的段
    private final ConcurrentHashMap<String, Long> sequenceSegmentMap = new ConcurrentHashMap<>();

    @Resource(name = "redisTemplateMap")
    private LinkedHashMap<String, RedisTemplate<String, Long>> redisTemplateMap;

    @Autowired
    private RedisConfig redisConfig;

    // redis状态Map
    private Map<String, Boolean> redisStatusOkMap = new HashMap<>();

    // Hash算法
    private final Hashing algo = Hashing.MURMUR_HASH;

    @Autowired
    private void initialize() {
        redisTemplateMap.keySet().forEach(rdName -> {
            // 初始化认为所有redis是OK的
            redisStatusOkMap.put(rdName, Boolean.TRUE);
            int N = 160;
            for (int n = 0; n < N; n++) {
                nodes.put(this.algo.hash(rdName + "*" + n), rdName);
            }
        });
    }

    // 取redis的服务名
    private String getTemplateName(String seqName) {
        SortedMap<Long, String> tail = nodes.tailMap(algo.hash(seqName));
        if (tail.isEmpty()) {
            return nodes.get(nodes.firstKey());
        }
        return tail.get(tail.firstKey());
    }

    // 取下一个redis的服务名
    private String neighborName(String templateName) {
        List<String> names = new ArrayList<>(redisTemplateMap.keySet());
        int i = names.indexOf(templateName);
        if (i >= 0 && i < names.size() - 1) {
            return names.get(i + 1);
        }
        else {
            return names.get(0);
        }
    }

    // 增加一个redis的步长
    public Long increment(String seqName, long delta) throws Exception {
        seqName = redisConfig.getKeyPrefix() + "_" + seqName;
        String redisName = getTemplateName(seqName);
        // 如果redis状态正常
        if (redisStatusOkMap.get(redisName)) {
            try {
                return increment(redisName, seqName, delta);
            }
            catch (Exception e) {
                // 第一次发现redis服务器挂了
                // 标记Redis状态为异常
                redisStatusOkMap.put(redisName, Boolean.FALSE);
                // 切换到相邻的redis，
                String newRedisName = neighborName(redisName);
                logger.info("原服务器:{},切换到:{}", redisName, newRedisName);
                return redisIncrement(newRedisName, seqName, delta, true);
            }
        }
        else {
            String newRedisName = neighborName(redisName);
            return increment(newRedisName, seqName, delta);
        }
    }

    // 调用redis的incr方法,默认不是切redis
    private Long increment(String redisName, String seqName, long delta) {
        return redisIncrement(redisName, seqName, delta, false);
    }

    /**
    * Description  调用redis的incr方法<br>
    * @Author wanjf <br>
    * @Date 2020/11/23 17:55 <br>
    * @param redisName redis服务器<br>
    * @param seqName 序列key<br>
    * @param delta 步长<br>
    * @param isChangeRedis true 切换新的redis   false 用老的redis<br>
    * @return java.lang.Long <br>
    **/
    private Long redisIncrement(String redisName, String seqName, long delta, boolean isChangeRedis) {
        RedisTemplate<String, Long> template = redisTemplateMap.get(redisName);
        BoundValueOperations<String, Long> operations = template.boundValueOps(seqName);

        if (isChangeRedis) { // 如果是切换redis 从内存取最新序列值，作为redis初始值
            Long latestSegment = sequenceSegmentMap.get(seqName);
            if (latestSegment == null) { // 刚开始map没有值, 不用设置
                logger.info("程序启动刚时，seqName:[{}] 在内存没有值 内存里面没有原序列", seqName);
            }
            else {
                operations.set(latestSegment);
            }
        }

        // 取最新的一个increment
        Long sequenceSegment = operations.increment(delta);
        sequenceSegmentMap.put(seqName, sequenceSegment);

        return sequenceSegment;
    }

}
